const path = require('path');
const compressing = require('compressing');
const moment = require('moment');
const fs = require('fs-extra');
const BaseReporter = require('./../base-report');
const { statusKind, kind } = require('../../../files');
const fileElements = require('../../../file-elements');
const functions = require('../../../functions');
const utility = require('../../../utility');
const Function = require('../../../mongo/function');
const File = require('../../../mongo/file');
const testbeds = require('../../../testbeds');
const functionRelations = require('../../../function-relations');
const ProgressIndicator = require('../../../progress-indicator');
class CodeQualityMeasureReporter extends BaseReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format) {
    super(versionId, locale, version, project, creator, outputDir, __dirname, format);
    this.reportName = this.i18n.__('tests.codeQualityMeasureReport');
    this.headerFilesExtNames = [];
    this.sourceFilesExtNames = [];
    this.functionCacheMap = new Map();
    this.typeSysMap = new Map();
  }

  /**
     * 报告生成
     */
  async generate(progressIndicator = new ProgressIndicator()) {
    const result = {
      cachePath: this.outputDir,
    };
    const [filesProgressIndicator, summaryProgressIndictor] = progressIndicator.split([0.9, 0.1]);
    const { versionPath } = await this.spliteByProjectVersion();
    await fs.ensureDir(versionPath);
    const filesData = await this.handleFiles(versionPath, filesProgressIndicator);
    await this.generateSummary(filesData);
    const fileName = `${this.project.projectName}-${this.version.versionName}-${this.reportName}-${this.format}.zip`;
    result.reportPath = path.join(this.outputDir, fileName);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    summaryProgressIndictor.finish();
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    return result;
  }

  /**
   * 批量处理文件
   * @param {String} versionDir 版本目录
   * @param {Number} filesProgressIndicator 进度比例
   * @return {Promise}
   */
  async handleFiles(versionDir, progressIndicator) {
    const { languages, headers } = this.version;
    if (languages) {
      Object.keys(languages).forEach((key) => {
        if (languages[key].suffixes) {
          this.sourceFilesExtNames.push(...languages[key].suffixes);
        }
      });
    }
    if (headers && headers.suffixes) {
      this.headerFilesExtNames = headers.suffixes;
    }
    const files = await File.find({
      versionId: this.versionId,
      status: statusKind.compiled,
      kind: kind.file,
      extname: { $in: [...this.headerFilesExtNames, ...this.sourceFilesExtNames] },
    });
    if (!files && files.length === 0) {
      progressIndicator.finish();
      return Promise.resolve();
    }
    const fileProgressIndicator = 1 / files.length;
    return utility.arrayChunkOperation(files, 1, async (file) => {
      const fileData = await this.handleFile(file, versionDir);
      progressIndicator.advance(fileProgressIndicator);
      return fileData;
    });
  }

  /**
 * 处理单文件
 * @param {Object} file 文件
 */
  async handleFile(file, versionDir) {
    const resFile = {
      reportName: this.reportName,
      fileName: file.fileName,
      totalLines: file.numberOfLines,
      emptyLines: file.emptyLines,
      executableLines: file.sourceLines,
      commentLines: file.commentLines,
      commentLinesRate: file.commentLines && file.numberOfLines ? `${(parseFloat(file.commentLines / file.numberOfLines) * 100).toFixed(2)}%` : '0', // 注释率
      isSourceFile: this.sourceFilesExtNames.includes(file.extname),
      functions: [],
      dateObjectAnalyzer: [],
      lifetimeAnalyzer: [],
    };
    // handle file dateObjectAnalyzer
    const dateObjectAnalyzer = await testbeds.getTestbedFlow({ fileId: file._id, ruleType: 'DateObjectAnalyzer' });
    dateObjectAnalyzer.forEach((obj) => {
      const objData = obj.data;
      if (!objData) {
        return;
      }
      resFile.dateObjectAnalyzer.push({
        name: objData.objectName,
        attrName: '',
        type: objData.kind,
        visibility: '',
        value: '',
      });
      if (!objData.attributes) {
        return;
      }
      Object.keys(objData.attributes).forEach((attrName) => {
        const attr = objData.attributes[attrName];
        resFile.dateObjectAnalyzer.push({
          name: objData.objectName,
          attrName,
          type: attr.attrType,
          visibility: attr.attrVisibility,
          value: attr.attrValue || '',
        });
      });
    });
    // handle file lifetimeAnalyzer
    const lifetimeAnalyzer = await testbeds.getTestbedFlow({ fileId: file._id, ruleType: 'LifetimeAnalyzer' });
    const funcMangledIdMap = new Map();
    (await Function.find({ fileId: file._id }, ['mangledId', 'functionFullName']).lean()).forEach((func) => {
      funcMangledIdMap.set(func.mangledId, func.functionFullName);
    });
    lifetimeAnalyzer.forEach((lifetime) => {
      const lifetimeData = lifetime.data;
      if (!lifetimeData) {
        return;
      }
      if (lifetimeData.scope === 'function' && !funcMangledIdMap.has(lifetime.functionMangledId)) {
        return;
      }
      const { begin, end } = lifetimeData.lifetime;
      let scope;
      switch (lifetimeData.scope) {
        case 'file': {
          scope = '文件';
          break;
        }
        case 'function': {
          scope = '函数';
          break;
        }
        case 'project': {
          scope = '项目';
          break;
        }
        default: break;
      }
      resFile.lifetimeAnalyzer.push({
        name: lifetimeData.variableName,
        scope,
        storage: lifetimeData.storage,
        fileName: file.fileName,
        funcName: funcMangledIdMap.has(lifetime.functionMangledId) ? funcMangledIdMap.get(lifetime.functionMangledId) : '',
        lifetime: `(${begin.row},${begin.column})-(${end.row},${end.column})`,
      });
    });
    const versionRelations = await functionRelations.getRelationsByVersion(this.versionId);
    resFile.functions = (await this.handleFunctions(file, versionRelations)).sort((a, b) => a.index - b.index);

    const outputPath = path.join(versionDir, file.path);
    await fs.ensureDir(path.dirname(outputPath));
    await this.generateFileReport(resFile, outputPath);
    await fs.writeFile(`${outputPath}_函数头注释.txt`, resFile.functions.map((func) => `${func.comment}\n${func.declaration}`).join('\n\n\n\n'));
    return resFile;
  }

  async handleFunctions(file, versionRelations) {
    const resFuncs = [];
    const funcs = await Function.find({ fileId: file._id, kind: { $in: fileElements.getCountableKinds() } },
      ['functionFullName', 'functionNameArgs', 'numberOfLines', 'sourceLines', 'commentLines', 'emptyLines', 'fanIn', 'fanOut',
        'cycleComplexity', 'essentialComplexity', 'loopDepthMax', 'loopCount', 'returnCount', 'exitCount', 'index']).lean();
    await utility.arrayChunkOperation(funcs, 1, async (func) => {
      const funcVariable = await functions.getFunctionVariables(func._id);
      const hasReturn = funcVariable && funcVariable.output && funcVariable.output['%'];
      let returnType = 'void';
      if (funcVariable.output && funcVariable.output['%']) {
        returnType = funcVariable.output['%']['@type'];
      }
      const clearPath = [];
      if (hasReturn) {
        const cfg = await functions.getFunctionCFG(func._id, null, false);
        Object.values(cfg.nodes).forEach((node) => {
          if (node.exitPreNode && node.kind !== functions.cfgNodeKind.returnStat) {
            clearPath.push(node.end.line);
          }
        });
      }
      const resFunc = {
        index: func.index,
        functionName: func.functionFullName,
        declaration: `${returnType} ${func.functionNameArgs};`,
        totalLines: func.numberOfLines || 0,
        emptyLines: func.emptyLines || 0,
        executableLines: func.sourceLines || 0,
        commentLines: func.commentLines || 0,
        commentLinesRate: func.numberOfLines && func.commentLines ? `${(parseFloat(func.commentLines / func.numberOfLines) * 100).toFixed(2)}%` : '0',
        fanIn: func.fanIn || 0,
        fanOut: func.fanOut || 0,
        cycleComplexity: func.cycleComplexity || 0,
        essentialComplexity: func.essentialComplexity || 0,
        loopDepthMax: func.loopDepthMax || 0,
        loopCount: func.loopCount || 0,
        returnCount: func.returnCount || 0,
        exitCount: func.exitCount || 0,
        clearPath: clearPath.map((l, i) => ({ value: `第${l}行的退出语句未包含返回值`, idx: i })),
        pathTracker: [],
        dependencyAnalyzer: [],
        flowStatisticsAnalyzer: [],
      };
      resFunc.comment = await functions.generateHeadComment(func._id, this.functionCacheMap, this.typeSysMap, versionRelations);
      // handle func pathTracker;
      const pathTracker = await testbeds.getTestbedFlow({ fileId: file._id, ruleType: 'PathTracker', functionId: func._id });
      pathTracker.forEach((variablePathTracker) => {
        const variableData = variablePathTracker.data;
        if (!variableData) {
          return;
        }
        const { paths } = variableData;
        const varNewPaths = [];
        paths.forEach((pathData, index) => {
          let newPath = `path${index + 1}:`;
          pathData.forEach((item, childIndex) => {
            const { begin, end } = item.range;
            let location = `[(${begin.row},${begin.column}),(${end.row},${end.column})]`;
            if (childIndex !== 0) {
              location = `-${location}`;
            }
            newPath += location;
          });
          varNewPaths.push({ path: `${newPath};` });
        });
        resFunc.pathTracker.push({
          name: variableData.variableName,
          paths: varNewPaths,
        });
      });
      // handle func dependencyAnalyzer;
      const dependencyAnalyzer = await testbeds.getTestbedFlow({ fileId: file._id, ruleType: 'DependencyAnalyzer', functionId: func._id });
      dependencyAnalyzer.forEach((variableDependencyAnalyzer) => {
        const variablesData = variableDependencyAnalyzer.data;
        if (!variablesData) {
          return;
        }
        Object.keys(variablesData).forEach((variableName) => {
          const variableData = variablesData[variableName];
          if (variableData.definitionType) {
            variableData.definitionType = variableData.definitionType === 'strong' ? '强定义' : '弱定义';
          }
          resFunc.dependencyAnalyzer.push({
            name: variableName,
            conditionalDependStrongly: [],
            conditionalDependWeakly: [],
            directDependStrongly: [],
            directDependWeakly: [],
            definitionType: '',
            ...variableData,
          });
        });
      });
      // handle func flowStatisticsAnalyzer
      const flowStatisticsAnalyzer = await testbeds.getTestbedFlow({ fileId: file._id, ruleType: 'Statistics', functionId: func._id });
      flowStatisticsAnalyzer.forEach((variableStatisticsAnalyzer) => {
        const variablesData = variableStatisticsAnalyzer.data;
        if (!variablesData) {
          return;
        }
        Object.keys(variablesData).forEach((variableName) => {
          const variableData = variablesData[variableName];
          resFunc.flowStatisticsAnalyzer.push({
            name: variableName,
            dataVolume: '',
            frequency: '',
            pathLen: '',
            ...variableData,
          });
        });
      });
      resFuncs.push(resFunc);
    });
    return resFuncs;
  }

  /**
   * 忽略单元集成判断
   */
  isIntegrationTest() {
    return false;
  }

  /**
 * 生成测试报告总览
 * @param {Array[Object]} files 文件
 * @returns
 */
  generateSummary(files) {
    const summary = {
      reportTime: moment().format('YYYY/MM/DD HH:mm:ss'),
      reportName: this.reportName,
      headerRightContent: this.reportName,
      overview: {
        projectName: this.project.projectName,
        versionName: this.version.versionName,
        fileCount: files.length, // 文件总数
        totalLines: 0, // 总代码行数
        emptyLines: 0, // 空行数
        executableLines: 0, // 可执行行数
        commentLines: 0, // 注释行数
        commentLinesRate: 0, // 注释率
      },
      headerFiles: {
        totalLines: 0,
        emptyLines: 0, // 空行数
        executableLines: 0, // 可执行行数
        commentLines: 0, // 注释行数
        commentLinesRate: 0, // 注释率
        files: [],
      },
      sourceFiles: {
        totalLines: 0,
        emptyLines: 0, // 空行数
        executableLines: 0, // 可执行行数
        commentLines: 0, // 注释行数
        commentLinesRate: 0, // 注释率
        files: [],
      },
    };
    files.forEach((file) => {
      const keys = ['overview'];
      if (file.isSourceFile) {
        summary.sourceFiles.files.push(file);
        keys.push('sourceFiles');
      } else {
        summary.headerFiles.files.push(file);
        keys.push('headerFiles');
      }
      keys.forEach((key) => {
        summary[key].totalLines += file.totalLines;
        summary[key].emptyLines += file.emptyLines;
        summary[key].executableLines += file.executableLines;
        summary[key].commentLines += file.commentLines;
      });
    });
    ['overview', 'headerFiles', 'sourceFiles'].forEach((key) => {
      summary[key].commentLinesRate = summary[key].commentLines && summary[key].totalLines
        ? `${(parseFloat(summary[key].commentLines / summary[key].totalLines) * 100).toFixed(2)}%` : '0';
    });
    return this.generateSummaryReport(
      summary,
      path.join(
        this.outputDir,
        this.project.projectName,
        this.version.versionName,
        'summary'
      )
    );
  }
}
module.exports = CodeQualityMeasureReporter;
